﻿using System;
using System.Collections.Generic;
using System.Linq;
using SmartDatingService;

namespace SmartDatingService {
    public partial class ServicePrototype {
        public ResultWithStatus<string> GetPersonalData(string sessionKeyHash) {
            byte[] sessionKey;
            lock (_sessionDataLock) {
                // Check whether session key hash and password are correct
                OperationStatus skCheck = checkSessionKey(sessionKeyHash);
                if (skCheck != OperationStatus.Success)
                    return ResultWithStatus<string>.NewErrorResult(skCheck);
                // Get session key to decrypt the password
                sessionKey = _activeSessionKeys[b(sessionKeyHash)];
            }

            Entities contextEntities = new Entities();
            try {
                Guid userId = userIdByHash(contextEntities, sessionKeyHash);
                PersonalData pdForUser = (from pd in contextEntities.PersonalDatas
                                          where (pd.usr_id == userId)
                                          select pd).FirstOrDefault();
                if (pdForUser == null) {
                    return ResultWithStatus<string>.NewErrorResult(OperationStatus.NoPersonalData);
                }

                return ResultWithStatus<string>.NewSuccess(s(CryptoImpl.
                    writeObjectEncrypted<PersonalData>(pdForUser, sessionKey)));
            } catch (Exception) {
                return ResultWithStatus<string>.NewErrorResult(OperationStatus.Failure);
            } finally {
                contextEntities.Dispose();
            }
        }

        public ResultWithStatus<string> GetImage(string sessionHash, Guid userId) {
            byte[] sessionKey;
            lock (_sessionDataLock) {
                // Check whether session key hash and password are correct
                OperationStatus skCheck = checkSessionKey(sessionHash);
                if (skCheck != OperationStatus.Success)
                    return ResultWithStatus<string>.NewErrorResult(skCheck);
                // Get session key to decrypt the password
                sessionKey = _activeSessionKeys[b(sessionHash)];
            }
            Entities contextEntities = new Entities();
            try {
                byte[] image = (from pd in contextEntities.PersonalDatas
                                where (pd.usr_id == userId)
                                select pd.picture).FirstOrDefault();
                return ResultWithStatus<string>.NewSuccess(s(image));
            } catch (Exception) {
                return ResultWithStatus<string>.NewErrorResult(OperationStatus.Failure);
            } finally {
                contextEntities.Dispose();
            }
        }

        public OperationStatus SetImage(string sessionHash, string imageContent) {
            byte[] sessionKey;
            lock (_sessionDataLock) {
                // Check whether session key hash and password are correct
                OperationStatus skCheck = checkSessionKey(sessionHash);
                if (skCheck != OperationStatus.Success)
                    return skCheck;
                // Get session key to decrypt the password
                sessionKey = _activeSessionKeys[b(sessionHash)];
            }

            byte[] imageBytes = imageContent == null ? null : b(imageContent);
            Entities contextEntities = new Entities();
            try {
                Guid userId = userIdByHash(contextEntities, sessionHash);
                PersonalData pdForUser = (from pd in contextEntities.PersonalDatas
                                where (pd.usr_id == userId)
                                select pd).FirstOrDefault();
                if (pdForUser == null) {
                    pdForUser = new PersonalData();
                    pdForUser.usr_id = userId;
                    contextEntities.PersonalDatas.AddObject(pdForUser);
                }
                pdForUser.picture = imageBytes;
                contextEntities.SaveChanges();
                return OperationStatus.Success;
            } catch (Exception) {
                return OperationStatus.Failure;
            } finally {
                contextEntities.Dispose();
            }
        }

        public ResultWithStatus<int> GetGender(string sessionHash, Guid userId) {
            byte[] sessionKey;
            lock (_sessionDataLock) {
                // Check whether session key hash and password are correct
                OperationStatus skCheck = checkSessionKey(sessionHash);
                if (skCheck != OperationStatus.Success)
                    return ResultWithStatus<int>.NewErrorResult(skCheck);
                // Get session key to decrypt the password
                sessionKey = _activeSessionKeys[b(sessionHash)];
            }
            Entities contextEntities = new Entities();
            try {
                int gender = (from pd in contextEntities.PersonalDatas
                                where (pd.usr_id == userId)
                                select pd.gender).FirstOrDefault();
                return ResultWithStatus<int>.NewSuccess(gender);
            } catch (Exception) {
                return ResultWithStatus<int>.NewErrorResult(OperationStatus.Failure);
            } finally {
                contextEntities.Dispose();
            }
        }

        public OperationStatus SetPersonalData(string sessionKeyHash, string personalData) {
            byte[] sessionKey;
            lock (_sessionDataLock) {
                // Check whether session key hash and password are correct
                OperationStatus skCheck = checkSessionKey(sessionKeyHash);
                if (skCheck != OperationStatus.Success)
                    return skCheck;
                // Get session key to decrypt the password
                sessionKey = _activeSessionKeys[b(sessionKeyHash)];
            }

            Entities contextEntities = new Entities();
            try {
                Guid userId = userIdByHash(contextEntities, sessionKeyHash);

                PersonalData pdForUser = (from pd in contextEntities.PersonalDatas
                                          where (pd.usr_id == userId)
                                          select pd).FirstOrDefault();

                // decrypt and deserialize pd
                PersonalData pdFromBytes = CryptoImpl.
                    readObjectEncrypted<PersonalData>(b(personalData), sessionKey);

                // don't forget to persist user id
                pdFromBytes.usr_id = userId;

                if (pdForUser == null) {
                    // add
                    contextEntities.PersonalDatas.AddObject(pdFromBytes);
                } else {
                    // modify
                    contextEntities.PersonalDatas.DeleteObject(pdForUser);
                    contextEntities.PersonalDatas.AddObject(pdFromBytes);
                }

                contextEntities.SaveChanges();
                return OperationStatus.Success;
            } catch (Exception) {
                return OperationStatus.Failure;
            } finally {
                contextEntities.Dispose();
            }
        }

        public OperationStatus SetLocation(string sessionKeyHash, double newLatitude, double newLongitude) {
            OperationStatus skStatus = checkSessionKey(sessionKeyHash);
            if (skStatus != OperationStatus.Success) return skStatus;

            Entities contextEntities = new Entities();
            try {
                Guid userId = userIdByHash(contextEntities, sessionKeyHash);

                IEnumerable<Location> locationForUser =
                     from loc in contextEntities.Locations
                     where (loc.usr_id == userId)
                     select loc;
                if (locationForUser.Count() == 0) {
                    contextEntities.Locations.AddObject(
                        new Location() {
                            usr_id = userId,
                            date_updated = DateTime.UtcNow,
                            latitude = newLatitude,
                            longitude = newLongitude
                        });
                } else {
                    Location locToChange = locationForUser.First();
                    locToChange.date_updated = DateTime.Now;
                    locToChange.latitude = newLatitude;
                    locToChange.longitude = newLongitude;
                }
                contextEntities.SaveChanges();
            } catch (Exception) {
                return OperationStatus.Failure;
            } finally {
                contextEntities.Dispose();
            }

            return OperationStatus.Success;
        }

        public ResultWithStatus<Pair<double, double>?> GetLocation(string sessionKeyHash) {
            OperationStatus skStatus = checkSessionKey(sessionKeyHash);
            if (skStatus != OperationStatus.Success)
                return ResultWithStatus<Pair<double, double>?>.
                    NewErrorResult(skStatus);

            Entities contextEntities = new Entities();
            try {
                Guid userId = userIdByHash(contextEntities, sessionKeyHash);

                IEnumerable<Location> userLocations =
                    from loc in contextEntities.Locations
                    where (loc.usr_id == userId)
                    select loc;

                if (userLocations.Count() == 0)
                    return ResultWithStatus<Pair<double, double>?>.
                        NewErrorResult(OperationStatus.NoLocation);

                Location currLoc = userLocations.First();

                return ResultWithStatus<Pair<double, double>?>.
                    NewSuccess(new Pair<double, double> {
                        First = Convert.ToDouble(currLoc.latitude),
                        Second = Convert.ToDouble(currLoc.longitude)
                    });
            } catch (Exception) {
                return ResultWithStatus<Pair<double, double>?>.
                    NewErrorResult(OperationStatus.Failure);
            } finally {
                contextEntities.Dispose();
            }
        }
    }
}